﻿using HuaweiCloud.SDK.Core;
using HuaweiCloud.SDK.Core.Auth;
using HuaweiCloud.SDK.Dns.V2;
using HuaweiCloud.SDK.Dns.V2.Model;

namespace xrwang.DDNS.HuaweiCloud
{
    /// <summary>
    /// 华为云域名记录操作
    /// </summary>
    public class DomainRecordOperator : IDomainRecordOperator
    {
        #region Const
        /// <summary>
        /// 默认的记录线路
        /// </summary>
        public const string DEFAULT_RECORD_LINE = "全网默认";
        #endregion Const

        /// <summary>
        /// 密匙id
        /// </summary>
        private string accessKeyID;
        /// <summary>
        /// 密匙id
        /// </summary>
        public string AccessKeyID
        {
            get
            {
                return accessKeyID;
            }
            set
            {
                if (string.IsNullOrWhiteSpace(value))
                    throw new ArgumentNullException(nameof(AccessKeyID), string.Format("{0}不能为空。", nameof(AccessKeyID)));
                accessKeyID = value;
                CreateClient();
            }
        }
        /// <summary>
        /// 密匙
        /// </summary>
        private string accessKeySecret;
        /// <summary>
        /// 密匙
        /// </summary>
        public string AccessKeySecret
        {
            get
            {
                return accessKeySecret;
            }
            set
            {
                if (string.IsNullOrWhiteSpace(value))
                    throw new ArgumentNullException(nameof(AccessKeySecret), string.Format("{0}不能为空。", nameof(AccessKeySecret)));
                accessKeySecret = value;
                CreateClient();
            }
        }
        /// <summary>
        /// 项目id
        /// </summary>
        private string projectId;
        /// <summary>
        /// 项目id
        /// </summary>
        public string ProjectId
        {
            get
            {
                return projectId;
            }
            set
            {
                if (string.IsNullOrWhiteSpace(value))
                    throw new ArgumentNullException(nameof(ProjectId), string.Format("{0}不能为空。", nameof(ProjectId)));
                projectId = value;
                CreateClient();
            }
        }
        /// <summary>
        /// 客户端
        /// </summary>
        private DnsAsyncClient client;

        /// <summary>
        /// 构造函数
        /// </summary>
        /// <param name="accessKeyID">密匙id</param>
        /// <param name="accessKeySecret">密匙</param>
        /// <param name="projectId">项目id</param>
        public DomainRecordOperator(string accessKeyID, string accessKeySecret, string projectId)
        {
            if (string.IsNullOrWhiteSpace(accessKeyID))
                throw new ArgumentNullException(nameof(AccessKeyID), string.Format("{0}不能为空。", nameof(AccessKeyID)));
            this.accessKeyID = accessKeyID;
            if (string.IsNullOrWhiteSpace(accessKeySecret))
                throw new ArgumentNullException(nameof(AccessKeySecret), string.Format("{0}不能为空。", nameof(AccessKeySecret)));
            this.accessKeySecret = accessKeySecret;
            if (string.IsNullOrWhiteSpace(projectId))
                throw new ArgumentNullException(nameof(ProjectId), string.Format("{0}不能为空。", nameof(ProjectId)));
            this.projectId = projectId;
            CreateClient();
        }

        /// <summary>
        /// 创建客户端
        /// </summary>
        private void CreateClient()
        {
            if (string.IsNullOrWhiteSpace(accessKeyID) || string.IsNullOrWhiteSpace(accessKeySecret) || string.IsNullOrWhiteSpace(projectId))
                client = null;
            else
            {
                HttpConfig config = HttpConfig.GetDefaultConfig();
                config.IgnoreSslVerification = true;
                client = DnsAsyncClient.NewBuilder()
                    .WithCredential(new BasicCredentials(accessKeyID, accessKeySecret, projectId))
                    .WithRegion(DnsRegion.CN_EAST_3)
                    .WithHttpConfig(config)
                    .WithLogging(LogLevel.Information)
                    .Build();
            }
        }

        /// <summary>
        /// 获取域名公共区域列表
        /// </summary>
        /// <param name="domainName">域名名称</param>
        /// <param name="isPublic">是否为公共区域</param>
        /// <param name="limit">返回的资源个数</param>
        /// <param name="offset">分页查询起始偏移量</param>
        /// <returns>返回域名公共区域列表</returns>
        public async Task<ActionResult<List<PublicZoneResp>>> ListPublicZones(string domainName = "", bool? isPublic = null, int limit = 500, int offset = 0)
        {
            ListPublicZonesRequest req = new ListPublicZonesRequest()
            {
                Name = domainName,
                Type = isPublic.HasValue ? (isPublic.Value ? "public" : "private") : "",
                Limit = limit,
                Offset = offset
            };
            try
            {
                var resp = await client.ListPublicZonesAsync(req);
                return new ActionResult<List<PublicZoneResp>>(true, "", resp.Zones);
            }
            catch (RequestTimeoutException e)
            {
                return new ActionResult<List<PublicZoneResp>>(false, string.Format("错误消息：{0}，提示：{1}",
                    e.ErrorMessage, e.Message), null);
            }
            catch (ServiceResponseException e)
            {
                return new ActionResult<List<PublicZoneResp>>(false,
                    string.Format("请求id：{0}，错误代码：{1}，错误消息：{2}，{3}，提示：{4}",
                    e.RequestId, e.ErrorCode, e.ErrorMsg, e.ErrorMessage, e.Message), null);
            }
            catch (ConnectionException e)
            {
                return new ActionResult<List<PublicZoneResp>>(false, string.Format("错误消息：{0}，提示：{1}",
                    e.ErrorMessage, e.Message), null);
            }
        }

        /// <summary>
        /// 获取域名名称对应的域名区域Id
        /// </summary>
        /// <param name="domainName"></param>
        /// <returns>返回域名区域Id</returns>
        private async Task<string> GetDomainZoneId(string domainName)
        {
            ActionResult<List<PublicZoneResp>> resp = await ListPublicZones(domainName);
            return resp.Success && resp.Data != null && resp.Data.Count > 0 ? resp.Data[0].Id : null;
        }

        /// <summary>
        /// 删除
        /// </summary>
        /// <param name="recordId">记录id</param>
        /// <param name="domainName">域名</param>
        /// <returns>返回结果。如果成功，Data为请求id。</returns>
        /// <remarks>华为云正常情况下不返回请求id，仅在发生ServiceResponseException时返回请求id。</remarks>
        public async Task<ActionResult<string>> DeleteAsync(string recordId, string domainName)
        {
            string zoneId = await GetDomainZoneId(domainName);
            if (string.IsNullOrWhiteSpace(zoneId))
                return new ActionResult<string>(false, "获取域名区域id失败。");
            var req = new DeleteRecordSetRequest()
            {
                RecordsetId = recordId,
                ZoneId = zoneId
            };
            try
            {
                DeleteRecordSetResponse response = await client.DeleteRecordSetAsync(req);
                return new ActionResult<string>(true, "", null);
            }
            catch (RequestTimeoutException e)
            {
                return new ActionResult<string>(false, string.Format("错误消息：{0}，提示：{1}",
                    e.ErrorMessage, e.Message), null);
            }
            catch (ServiceResponseException e)
            {
                return new ActionResult<string>(false,
                    string.Format("请求id：{0}，错误代码：{1}，错误消息：{2}，{3}，提示：{4}",
                    e.RequestId, e.ErrorCode, e.ErrorMsg, e.ErrorMessage, e.Message), e.RequestId);
            }
            catch (ConnectionException e)
            {
                return new ActionResult<string>(false, string.Format("错误消息：{0}，提示：{1}",
                    e.ErrorMessage, e.Message), null);
            }
        }

        /// <summary>
        /// 获取域名记录列表
        /// </summary>
        /// <param name="domainName">域名名称</param>
        /// <param name="rr">主机记录</param>
        /// <param name="type">记录类型</param>
        /// <param name="value">记录值</param>
        /// <param name="line">解析线路（华为云忽略该参数）</param>
        /// <param name="page">页码</param>
        /// <param name="pageSize">每页记录数</param>
        /// <returns>返回结果。如果成功，Data1为域名记录信息列表，Data2为记录总数。</returns>
        public async Task<ActionResult<List<DomainRecordInfo>, int>> GetListAsync(string domainName, string rr = "", DomainRecordTypeEnum? type = null, string value = "", string line = "", int page = 1, int pageSize = 100)
        {
            string zoneId = await GetDomainZoneId(domainName);
            if (string.IsNullOrWhiteSpace(zoneId))
                return new ActionResult<List<DomainRecordInfo>, int>(false, "获取域名区域id失败。");
            var req = new ListRecordSetsByZoneRequest()
            {
                ZoneId = zoneId,
                Limit = pageSize,
                Offset = (page - 1) * pageSize,
                Type = type.HasValue ? type.Value.ToString("g") : "",
                Name = rr
            };
            try
            {
                ListRecordSetsByZoneResponse response = await client.ListRecordSetsByZoneAsync(req);
                return new ActionResult<List<DomainRecordInfo>, int>(true, "",
                    response.Recordsets == null || response.Recordsets.Count == 0 ? null : response.Recordsets
                    .Where(rec => rec.Records != null && rec.Records.Count > 0)
                    .SelectMany(
                        rec => rec.Records.Select(
                            value => new DomainRecordInfo()
                            {
                                RecordId = rec.Id,
                                DomainName = rec.ZoneName,
                                RR = rec.Name,
                                Type = rec.Type,
                                Value = value,
                                TTL = rec.Ttl,
                                Priority = null,
                                Line = "",
                                Locked = rec.Status == DomainRecordStatusEnum.FREEZE.ToString(),
                                Status = rec.Status == DomainRecordStatusEnum.ENABLE.ToString("g") ||
                                    rec.Status == DomainRecordStatusEnum.ACTIVE.ToString("g") ||
                                    rec.Status == DomainRecordStatusEnum.PENDING_CREATE.ToString("g") ||
                                    rec.Status == DomainRecordStatusEnum.PENDING_UPDATE.ToString("g")
                            })).ToList(),
                    response.Metadata != null && response.Metadata.TotalCount.HasValue ? response.Metadata.TotalCount.Value : 0);
            }
            catch (RequestTimeoutException e)
            {
                return new ActionResult<List<DomainRecordInfo>, int>(false, string.Format("错误消息：{0}，提示：{1}",
                    e.ErrorMessage, e.Message));
            }
            catch (ServiceResponseException e)
            {
                return new ActionResult<List<DomainRecordInfo>, int>(false,
                    string.Format("请求id：{0}，错误代码：{1}，错误消息：{2}，{3}，提示：{4}",
                    e.RequestId, e.ErrorCode, e.ErrorMsg, e.ErrorMessage, e.Message));
            }
            catch (ConnectionException e)
            {
                return new ActionResult<List<DomainRecordInfo>, int>(false, string.Format("错误消息：{0}，提示：{1}",
                    e.ErrorMessage, e.Message));
            }
        }

        /// <summary>
        /// 设置状态
        /// </summary>
        /// <param name="recordId">记录id</param>
        /// <param name="domainName">域名名称（华为云忽略域名）</param>
        /// <returns>返回结果。如果成功，Data为请求id。</returns>
        /// <remarks>华为云正常情况下不返回请求id，仅在发生ServiceResponseException时返回请求id。</remarks>
        public async Task<ActionResult<string>> SetStatusAsync(string recordId, string domainName = "", bool enabled = true)
        {
            ulong id;
            if (!ulong.TryParse(recordId, out id))
                return new ActionResult<string>(false, "记录id无效。");
            SetRecordSetsStatusRequest request = new SetRecordSetsStatusRequest()
            {
                RecordsetId = recordId
            };
            request.Body = new SetRecordSetsStatusReq()
            {
                Status = (enabled ? DomainRecordStatusEnum.ENABLE : DomainRecordStatusEnum.DISABLE).ToString("g")
            };
            try
            {
                SetRecordSetsStatusResponse response = await client.SetRecordSetsStatusAsync(request);
                return new ActionResult<string>(true, "");
            }
            catch (RequestTimeoutException e)
            {
                return new ActionResult<string>(false, string.Format("错误消息：{0}，提示：{1}",
                    e.ErrorMessage, e.Message), null);
            }
            catch (ServiceResponseException e)
            {
                return new ActionResult<string>(false,
                    string.Format("请求id：{0}，错误代码：{1}，错误消息：{2}，{3}，提示：{4}",
                    e.RequestId, e.ErrorCode, e.ErrorMsg, e.ErrorMessage, e.Message), e.RequestId);
            }
            catch (ConnectionException e)
            {
                return new ActionResult<string>(false, string.Format("错误消息：{0}，提示：{1}",
                    e.ErrorMessage, e.Message), null);
            }
        }

        /// <summary>
        /// 添加
        /// </summary>
        /// <param name="record">域名记录</param>
        /// <returns>返回结果。如果成功，Data1为请求id，Data2为记录id。</returns>
        /// <remarks>华为云正常情况下不返回请求id，仅在发生ServiceResponseException时返回请求id。</remarks>
        public async Task<ActionResult<string, string>> AddAsync(DomainRecordInfo record)
        {
            string zoneId = await GetDomainZoneId(record.DomainName);
            if (string.IsNullOrWhiteSpace(zoneId))
                return new ActionResult<string, string>(false, "获取域名区域id失败。");
            CreateRecordSetRequest request = new CreateRecordSetRequest()
            {
                ZoneId = zoneId
            };
            request.Body = new CreateRecordSetReq()
            {
                Name = string.IsNullOrWhiteSpace(record.RR) ? record.DomainName :
                    (record.RR.EndsWith(record.DomainName, StringComparison.OrdinalIgnoreCase) ? record.RR : string.Format("{0}.{1}", record.RR, record.DomainName)),
                Type = record.Type,
                Status = (record.Status ? DomainRecordStatusEnum.ENABLE : DomainRecordStatusEnum.DISABLE).ToString("g"),
                Ttl = (int?)record.TTL,
                Records = new List<string>() { record.Value }
            };
            try
            {
                var response = await client.CreateRecordSetAsync(request);
                return new ActionResult<string, string>(true, "", null, response.Id);
            }
            catch (RequestTimeoutException e)
            {
                return new ActionResult<string, string>(false, string.Format("错误消息：{0}，提示：{1}",
                    e.ErrorMessage, e.Message));
            }
            catch (ServiceResponseException e)
            {
                return new ActionResult<string, string>(false,
                    string.Format("请求id：{0}，错误代码：{1}，错误消息：{2}，{3}，提示：{4}",
                    e.RequestId, e.ErrorCode, e.ErrorMsg, e.ErrorMessage, e.Message), e.RequestId, null);
            }
            catch (ConnectionException e)
            {
                return new ActionResult<string, string>(false, string.Format("错误消息：{0}，提示：{1}",
                    e.ErrorMessage, e.Message));
            }
        }

        /// <summary>
        /// 修改
        /// </summary>
        /// <param name="record">域名记录</param>
        /// <returns>返回结果。如果成功，Data1为请求id，Data2为记录id。</returns>
        /// <remarks>华为云正常情况下不返回请求id，仅在发生ServiceResponseException时返回请求id。</remarks>
        public async Task<ActionResult<string, string>> UpdateAsync(DomainRecordInfo record)
        {
            string zoneId = await GetDomainZoneId(record.DomainName);
            if (string.IsNullOrWhiteSpace(zoneId))
                return new ActionResult<string, string>(false, "获取域名区域id失败。");
            UpdateRecordSetRequest request = new UpdateRecordSetRequest()
            {
                ZoneId = zoneId,
                RecordsetId = record.RecordId
            };
            request.Body = new UpdateRecordSetReq()
            {
                Name = string.IsNullOrWhiteSpace(record.RR) ? record.DomainName :
                    (record.RR.EndsWith(record.DomainName, StringComparison.OrdinalIgnoreCase) ? record.RR : string.Format("{0}.{1}", record.RR, record.DomainName)),
                Type = record.Type,
                Ttl = (int?)record.TTL,
                Records = new List<string>() { record.Value }
            };
            try
            {
                var response = await client.UpdateRecordSetAsync(request);
                await SetStatusAsync(record.RecordId, record.DomainName, record.Status);
                return new ActionResult<string, string>(true, "", null, response.Id);
            }
            catch (RequestTimeoutException e)
            {
                return new ActionResult<string, string>(false, string.Format("错误消息：{0}，提示：{1}",
                    e.ErrorMessage, e.Message));
            }
            catch (ServiceResponseException e)
            {
                return new ActionResult<string, string>(false,
                    string.Format("请求id：{0}，错误代码：{1}，错误消息：{2}，{3}，提示：{4}",
                    e.RequestId, e.ErrorCode, e.ErrorMsg, e.ErrorMessage, e.Message), e.RequestId, null);
            }
            catch (ConnectionException e)
            {
                return new ActionResult<string, string>(false, string.Format("错误消息：{0}，提示：{1}",
                    e.ErrorMessage, e.Message));
            }
        }

        /// <summary>
        /// 保存
        /// </summary>
        /// <param name="record">域名记录</param>
        /// <returns>返回结果。如果成功，Data1为请求id，Data2为记录id。</returns>
        public async Task<ActionResult<string, string>> SaveAsync(DomainRecordInfo record)
        {
            ActionResult<List<DomainRecordInfo>, int> result = await GetListAsync(record.DomainName, record.RR);
            DomainRecordInfo rec = result.Success && result.Data1 != null ? result.Data1.FirstOrDefault(r => string.Compare(r.RR, record.RR, true) == 0) : null;
            if (rec != null)
            {
                record.RecordId = rec.RecordId;
                if (string.Compare(rec.Value, record.Value, true) != 0 || rec.Status != record.Status)
                    return await UpdateAsync(record);
                else
                    return new ActionResult<string, string>(true, "域名记录没有变化。");
            }
            else
                return await AddAsync(record);
        }
    }
}